﻿using System;
using System.Text;
using System.Windows.Forms;
using DigitalRune.Windows.Docking;
using md.imi.membranes.gui.Dialogs;
using md.imi.membranes.gui.Editor;
using md.imi.membranes.objects.membranes;
using md.imi.membranes.objects.psystems;
using md.imi.membranes.objects.strings;

namespace md.imi.membranes.gui.Navigation
{
	using md.imi.membranes.objects;
	using System.Collections.Generic;

	/// <summary>
	///
	/// </summary>
	public partial class ComputationTree : DockableForm
	{
	    private MainForm parent;
	    //public MainForm Parent { get {return parent;} }
	    
		private enum NavigationDirection
		{
			NavigateNone,
			NavigateForward,
			NavigateBackward
		} ;

		NavigationDirection navigationDirection = NavigationDirection.NavigateNone;

		#region External events 
		public delegate void ConfigurationSelectionHandler(PSystemsTree solution);
		public event ConfigurationSelectionHandler NotifyConfigurationSelection;
		#endregion

		/// <summary>
		///     THe flag is set to true, when selection is performed as a navigation request
		/// </summary>
		//private bool isNavigationReques = false;
		
		/// <summary>
		///     Limit history number of entties
		/// </summary>
		private const int MAX_NAVIGATION_ENTRIES = 15;
		/// <summary>
		///  Hold all navigation steps performed by user.
		/// </summary>
		private List<Guid> navigationHistory = new List<Guid>();

		/// <summary>
		///     Pointer in navigationHistory to active configuration.
		/// </summary>
		//private Guid activeConfiguration; 

		private enum Images
		{
			imgPSysConfig   = 0,
			imgSolution     = 1,
		}

		public PSystemsTree Computations { get; set; }

		/// <summary>
		///
		/// </summary>
		public ComputationTree(MainForm parent)
		{
		    this.parent = parent;
			InitializeComponent();
		}

		/// <summary>
		///
		/// </summary>
		/// <param name="computationTree"></param>
		public void UpdateComputationTree(PSystemsTree computationTree)
		{
			
			tvComputations.Nodes.Clear();

			if (computationTree == null)
			{
				return;
			}

			tvComputations.Nodes.Clear();
			
			// add null node (current configuration)
		    Computations = computationTree;
			
			if (String.IsNullOrEmpty(computationTree.ProjectName))
			{
			    computationTree.ProjectName = GeneratePsystemProjectName("0", -1);
			}
			
			String ctName = String.IsNullOrEmpty(computationTree.BaseLabel) ? "0" : computationTree.BaseLabel;
		    
			var newNode = new TreeNode(GeneratePsystemTitle(ctName, -1, Computations))
			{
				Tag = Computations,
				ImageIndex = (int)Images.imgPSysConfig
			};

			tvComputations.Nodes.Add(newNode);
			
			    
			PopulateComputationTreeNodes(newNode, Computations, ctName);
			TabText = String.Format("ComputationTree - {0}", computationTree.ProjectName);
		}

		/// <summary>
		///     
		/// </summary>
		/// <param name="tree"></param>
		/// <returns></returns>
		private string GeneratePsystemTitle(string level, int levelId, PSystemsTree tree)
		{
			if (tree == null)
			{
				return "<NULL>";
			}
			
			var variants = "?";
			if (tree.PSystems != null)
			{
				variants = tree.PSystems.Count.ToString();
			}
            else if (tree.PathsCalculated)
            {
                variants = "0";
            }
			
			if (levelId >= 0)
			{
			    return String.Format("{0} ({1} variants)",  String.Format("{0}.{1}",level, levelId), variants);
			}
			else
			{
			    return String.Format("{0} ({1} variants)",  level, variants);
			}
		}
		
		private string GeneratePsystemProjectName(string level, int levelId)
		{
			if (levelId >= 0)
			{
		        return String.Format("config-{0}", String.Format("{0}.{1}",level, levelId));
			}
			else
			{
			    return String.Format("config-{0}", level);
			}
		}

		/// <summary>
		///     
		/// </summary>
		/// <param name="tree"></param>
		/// <returns></returns>
		private string GenerateSolutionTitle(string level, int levelId, List<Solution> solutions)
		{
			if (solutions == null)
			{
				return "<NULL>";
			}
			
			//return String.Format("{0} ({1} variants)", level, solutions.Count);
			return String.Format("{0} (not eval.)", level);
		}
		
		/// <summary>
		///     Scan level one computation tree, and determine if passed solution
		/// has been evaluated by checking source solution
		/// </summary>
		/// <param name="solution"></param>
		/// <param name="computationTree"></param>
		/// <returns></returns>
		private bool IsSolutionComputed(List<Solution> solution, List<PSystemsTree> computationTree)
		{
			// foreach(var 
			return true;
		}

		/// <summary>
		///     Recursively populate tree using computation tree nodes
		/// </summary>
		/// <param name="parentNode"></param>
		/// <param name="computationTree"></param>
		private void PopulateComputationTreeNodes(TreeNode parentNode, PSystemsTree computationTree, string level)
		{
			int levelId = 0;
			//-----------------------------------------------------------------
			// check parameters
			if (computationTree == null)
			{
				Console.WriteLine("NULL parameter passed to function");
				return;
			}
			
			//-----------------------------------------------------------------
			// populate evaluated solutions in PSystem
			if (computationTree.PSystems != null)
			{
				foreach (var psys in computationTree.PSystems)
				{
				    var label = String.IsNullOrEmpty(psys.BaseLabel) ? "" 
				        : String.Format("{0}.", psys.BaseLabel);
				    levelId += 1;
				    if (psys.ProjectName == null)
				    {
				        psys.ProjectName = GeneratePsystemProjectName(level, levelId);
				    }
				    
				    psys.Label = String.Format("{0}{1}.{2}", label, level, levelId);
				    
					var newNode = new TreeNode(GeneratePsystemTitle(level, levelId, psys))
					{
						Tag = psys,
						ImageIndex = (int)Images.imgPSysConfig
					};


				    newNode.ToolTipText = SolutionsToToolTip(psys.SourceSolution);

				    if (parentNode != null)
						parentNode.Nodes.Add(newNode);
					else
						tvComputations.Nodes.Add(newNode);
					
					if (psys.PSystems != null && psys.PSystems.Count > 0)
					{
						PopulateComputationTreeNodes(newNode, psys, String.Format("{0}.{1}", level, levelId));
					}
				}
			}
			
			/*
			//-----------------------------------------------------------------
			//  Draw also solutions that was not estimated (leaf nodes)
			if (computationTree.Solutions != null)
			{
				foreach(var solution in computationTree.Solutions)
				{
					// do not show computed solution, it will be shown lately, 
					// as a PSystem tree configuration
					if (!IsSolutionComputed(solution, computationTree.PSystems))
					{
						continue;    
					}
					
					var newNode = new TreeNode(GenerateSolutionTitle(
						String.Format("{0}.{1}", level, levelId ++ ), solution))
					{
						NodeFont = new System.Drawing.Font(System.Drawing.FontFamily.GenericSansSerif,
							 9.0F, System.Drawing.FontStyle.Italic),
						Tag = solution,
						ImageIndex = (int)Images.imgSolution
					};
						
					if (parentNode != null)
						parentNode.Nodes.Add(newNode);
					else
						tvComputations.Nodes.Add(newNode);
				}
			}
			*/
			
		}

	    private string SolutionsToToolTip(List<Solution> sourceSolution)
	    {
            if (sourceSolution == null || sourceSolution.Count == 0)
            {
                return String.Empty;
            }

	        var solutionsInfo = new StringBuilder();
	        foreach (var solution in sourceSolution)
	        {
	            solutionsInfo.Append(solution.ToString()).AppendLine();

	        }

	        return solutionsInfo.ToString();
	    }

	    /// <summary>
		///     Prepare to show info items of menu
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void ContextMenuStrip1Opening(object sender, System.ComponentModel.CancelEventArgs e)
		{
			var selectedNode = tvComputations.SelectedNode;

            computeToolStripMenuItem.Enabled = false;
            loadToolStripMenuItem.Enabled = false;
            renameToolStripMenuItem.Enabled = false;

            if (selectedNode == null || selectedNode.Tag == null)
			{
                HideInfoMenuItems(); 
				return;
			}


            // check if this has solutions to compute
            if (selectedNode.Tag is List<Solution>)
            {
                computeToolStripMenuItem.Enabled = false;
                return;
            }

            // cehck if this is a computed solution and it can beloaded or renamed
            if (selectedNode.Tag is PSystemsTree)
            {
                renameToolStripMenuItem.Enabled = true;
                loadToolStripMenuItem.Enabled = true;

            }

            /*
			// display solution information
			if (selectedNode.Tag is List<Solution>)
			{
				var solutions = selectedNode.Tag as List<Solution>;
				
				ShowInfoMenuItems(
					String.Format("Possible solutions: {0}", solutions.Count),
					"Solutions _ 2",  
					"Solutions _ 3");
			}
			
			// display pSystem information
			if (selectedNode.Tag is PSystemsTree)
			{
				ShowInfoMenuItems("PSystems _ 1", "PSystems _ 2",  "PSystems _ 3");
			}
            */
		}
		
		/// <summary>
		///     Show info menu items and set text
		/// </summary>
		/// <param name="info_1"></param>
		/// <param name="info_2"></param>
		/// <param name="info_3"></param>
		private void ShowInfoMenuItems(string info_1, string info_2,  string info_3)
		{
			// visibility
			menuItemSeparator.Visible = true;
			mnuInfo_1.Visible = true;
			mnuInfo_2.Visible = true;
			mnuInfo_3.Visible = true;
			
			// text
			mnuInfo_1.Text = info_1;
			mnuInfo_2.Text = info_2;
			mnuInfo_3.Text = info_3;
		}
		
		/// <summary>
		///     Hide information menu items
		/// </summary>
		private void HideInfoMenuItems()
		{
			menuItemSeparator.Visible = false;
			mnuInfo_1.Visible = false;
			mnuInfo_2.Visible = false;
			mnuInfo_3.Visible = false;
		}
		
		/// <summary>
		/// 
		///	After item selection has been changed,
		/// show in model browser the rule which has been evaluated to
		/// evaluate to the selected state
		/// </summary>
		/// <param name="sender">sender object</param>
		/// <param name="e">seletced node info</param>
		void TvComputationsAfterSelect(object sender, TreeViewEventArgs e)
		{
			if ((e.Node     == null) ||
				(e.Node.Tag == null) ||
				((e.Node.Tag is PSystemsTree) == false))
			{
				return;
			}
					
			var ps = (e.Node.Tag as PSystemsTree);

			//-----------------------------------------------------------------------------------------------------
			// after a configuratoin is selected:
			// 1. check if configuration has been evaluated
			if (ps.SourceSolution == null)
			{
				navigationDirection = NavigationDirection.NavigateNone;
				return;
			}
	
			//-----------------------------------------------------------------------------------------------------
			// 2. load configuration into editor
			if (NotifyConfigurationSelection != null)
			{
				NotifyConfigurationSelection(ps);
			}
	
			//-----------------------------------------------------------------------------------------------------
			// 3. add navigation point to navigaion history
			// navigation occured, then we will not need to stored postion into navigation history
			if (navigationDirection != NavigationDirection.NavigateNone)
			{
				return;
			}

			navigationDirection = NavigationDirection.NavigateNone;
			
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void btnNext_Click(object sender, EventArgs e)
		{
			navigationDirection = NavigationDirection.NavigateForward;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void btnBack_Click(object sender, EventArgs e)
		{
			navigationDirection = NavigationDirection.NavigateBackward;

		}

        /// <summary>
        ///     On computation click, performs one step, in manual mode, and update all GUI information
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
		private void computeToolStripMenuItem_Click(object sender, EventArgs e)
        {
		    
            LoadConfigurationIntoEditor();
        }

        /// <summary>
        /// 
        /// </summary>
        void LoadConfigurationIntoEditor ()
        {
            //-------------------------------------------------------------------------------------
            // validate input data
            if (tvComputations.SelectedNode == null)
            {
                Console.WriteLine("Null selection, ignore");
                return;
            }

            var ps = tvComputations.SelectedNode.Tag as PSystemsTree;
            if (ps == null)
            {
                Console.WriteLine("Wrong system selection");
                return;
            }

            //-------------------------------------------------------------------------------------
            // 1. search if this project has not been loaded already
            foreach (var p in WorkSpace.Instance.Projects)
            {
                if (p.Id.Equals(ps.Id))
                {
                    parent.CreateEditorFormForSolution(ps);
                    return;
                }
            }
            
            //-------------------------------------------------------------------------------------
            // 2. add project to projects list
            WorkSpace.Instance.Projects.Add(ps);
            parent.CreateEditorFormForSolution(ps);

        }

	    private void DumpMembranes(MembranesTree membranes)
	    {
            if (membranes == null)
            {
                return;
            }

            Console.WriteLine(" ---------------- {0} ------------------", membranes.ToString());
	        //DumpMultisets(membranes.Multiset);
            foreach (var ms in membranes.Multiset)
            {
                Console.WriteLine(ms.ToString());
            }
            
            Console.WriteLine();
            if (membranes.Membranes == null)
            {
                return;
            }

	        foreach (var m in membranes.Membranes)
	        {
	            DumpMembranes(m);
	        }
	    }

	    /// <summary>
        ///     Double click on a computation tree item, load the underlaing solution (if available) 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void tvComputations_DoubleClick(object sender, EventArgs e)
        {
            LoadConfigurationIntoEditor();
        }

        /// <summary>
        ///     Invoke rename dialog. The custom user name for this configuration 
        /// will be set
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void renameToolStripMenuItem_Click(object sender, EventArgs e)
        {
            var selectedNode = tvComputations.SelectedNode;
            var ps = selectedNode.Tag as PSystemsTree;

            if (ps == null)
            {
                // cannot rename  the information provided by this node
                return;
            }

            var renameForm = new FrmRenameConfiguration();

            renameForm.SetOldConfigurationName(ps.Label);
            if (renameForm.ShowDialog() == DialogResult.OK)
            {
                ps.Label = renameForm.GetNewConfigurationName();
                selectedNode.Text = ps.Label;
            }

        }
        
        private List<TreeNode> GetCheckedNodesList (TreeNodeCollection nodes)
        {
            List<TreeNode> selectedNodes = new List<TreeNode>();
            foreach (TreeNode node in nodes)
            {
                if (node.Checked) selectedNodes.Add(node);
                if (node.Nodes.Count > 0)
                {
                    selectedNodes.AddRange(GetCheckedNodesList(node.Nodes));
                }
            }
            return selectedNodes;
        }

        /**
         *      Allow only 2 concomitent selected tree nodes, to make comparation
         */
        private void tvComputations_BeforeCheck(object sender, TreeViewCancelEventArgs e)
        {
            //List<TreeNode> selectedNodes = GetCheckedNodesList(tvComputations.Nodes);
            //if (selectedNodes.Count >= 2)
            //{
            //    FrmMessageBox.ShowError("Diff can be performed only between two nodes, do not select more than two nodes.");
            //    e.Cancel = true;
            //}

        }

        private void btnDiff_Click(object sender, EventArgs e)
        {
            List<TreeNode> selectedNodes = GetCheckedNodesList(tvComputations.Nodes);

            if (selectedNodes.Count != 2)
            {
                FrmMessageBox.ShowError("Diff can be performed only between two nodes, do not select more than two nodes.");
                return;
            }

            
            if (parent == null)
            {
                FrmMessageBox.ShowError("Fatal error. Diff dialog cannot be accessed");
                return;
            }

            parent.DiffView.ShowNodesDiff((PSystemsTree)selectedNodes[0].Tag, (PSystemsTree)selectedNodes[1].Tag);
        }

	}
}